package application.models.models;

import java.util.HashSet;
import java.util.Set;
import java.io.IOException;

import application.models.Observer;
import application.models.collections.ReservationCollection;
import application.models.datalayer.Attribute;
import application.models.datalayer.Operator;
import application.models.datalayer.DataCondition;
import application.models.exceptions.InvalidAttributeException;

/**
 * A subclass of AbstractModel. Describes customers.
 * 
 * @author Morten Therkildsen & Philip Rasmussen
 */
public class CustomerModel extends AbstractModel {
	private static SetDescription setDescription = new SetDescription("customer_no", "customers", null, null, null);
	private static Set<Observer> globalObservers = new HashSet<Observer>(); // A set containing all global observers for any model of this kind
	private static boolean isIterating = false;
	
	/**
	 * The id attribute of the main set
	 * @return The id attribute of the main set
	 */
	public static String getMainSetIdAttribute(){

		return setDescription.getMainSetIdAttribute();
	}

	/**
	 * The name of the main set
	 * @return The name of the main set
	 */
	public static String getMainSetName(){

		return setDescription.getMainSetName();
	}

	/**
	 * The join attribute of the main set
	 * @return The join attribute of the main set
	 */
	public static String getMainSetJoinAttribute(){

		return setDescription.getMainSetJoinAttribute();
	}

	/**
	 * The name of the joined set
	 * @return The name of the joined set
	 */
	public static String getJoinedSetName(){

		return setDescription.getJoinedSetName();
	}

	/**
	 * The join attribute of the joined set
	 * @return The join attribute of the joined set
	 */
	public static String getJoinedSetJoinAttribute(){
		
		return setDescription.getJoinedSetJoinAttribute();
	}	
	
	/**
	 * Notifies all globally registered observers about a model update
	 * 
	 * While running global observers cannot be added, removed or notified
	 * 
	 * @param object	The model that observers should be notified about
	 */
	protected static void notifyGlobalObservers(CustomerModel object){
		
		if(isIterating) return;
		
		isIterating = true;
		for(Observer observer : globalObservers){
			observer.notify(object);
		}
		isIterating = false;
	}
	
	/**
	 * Registers an observer to receive notifications about any object of this kind
	 * An observer may only be added once - subsequent adds of the same observer are ignored
	 * 
	 * @param observer		The observer to add
	 * @return	true if the observer successfully was added, otherwise false
	 */
	public static boolean addGlobalObserver(Observer observer){

		if(observer == null) throw new NullPointerException("observer was null!");
		
		if(!isIterating){
			globalObservers.add(observer);
			return true;
		}
		
		return false;
	}

	/**
	 * Removes a previously set global observer
	 * Nothing will happen if observer has not been added beforehand
	 * 
	 * @param observer		A previously added observer
	 * @return	true if the observer successfully was removed, otherwise false
	 */
	public static boolean removeGlobalObserver(Observer observer){

		if(!isIterating){
			globalObservers.remove(observer);
			return true;
		}
		
		return false;
	}
	
	/**
	 * Loads an entity of this model type that matches the id value 
	 * @param The value of the id attribute for the entity
	 * @return The entity matching the value of the id attribute
	 */
	public static CustomerModel load(int id) {
		
		try{
			
			/* Unchecked type casting is okay, since application.models.models.CustomerModel is in fact of type CustomerModel */
			return (CustomerModel) loadHelper(id, (Class<CustomerModel>) Class.forName("application.models.models.CustomerModel"), setDescription);
		}
		catch(ClassNotFoundException e){throw new RuntimeException("Unexpected logic error. Update package name for the model.", e);}		
	}
	
	/**
	 * Creates an entity of this model type
	 * @param mainSetIdAttributeValue the value of the id attribute
	 * @param mainSetJoinAttributeValue the value of the sets joinAttribute
	 */
	public CustomerModel(Object mainSetIdAttributeValue, Object mainSetJoinAttributeValue){
		
		super(mainSetIdAttributeValue, mainSetJoinAttributeValue, setDescription);
	}

	/**
	 * Creates an entity of this model type
	 */
	public CustomerModel(){
		
		this(null, null);
	}
	
	/**
	 * Returns a collection of all reservation entities tied to this customer
	 * @return ReservationCollection of all reservation entities
	 */
	public ReservationCollection getReservations() throws IOException {
		
		ReservationCollection collection = new ReservationCollection();
		
		try{
			collection.addFilter(new DataCondition("customer_no", Operator.EQUALS, new Integer(getId())));
			collection.load();
		} catch(InvalidAttributeException e){
			throw new RuntimeException("The attribute \"customer_no\" doesn't exist in the data layer");
		}
			
		return collection;
	}
	
	/**
	 * Notifies all observers when called
	 */
	protected void notifyObservers(){

		super.notifyObservers();
		notifyGlobalObservers(this);
	}
	
	/**
	 * Validates that an attribute have met the data layer criteria
	 *  
	 * @param An attribute object
	 * @return	True if validation succeeds, false if attribute has a null value
	 */
	protected boolean mandatoryAttributeValidation(Attribute attribute){

		/* If a mandatory attribute is null it will cause a runtime exception. */
				if(attribute.isMandatory() && attribute.getValue() == null) return false;
				
		return true;
	}
	
	/**
	 * Validates that the object's value is valid. (Must not be empty)
	 */
	protected boolean attributeValueValidation(Object value){
		
		if(value instanceof String)
			if(value.toString().trim().length() == 0) return false;
		
		return true;
	}
}